//
// Created by Administrator on 2019/9/19.
//

#include <iostream>
#include <vector>
#include <malloc.h>
#include "Shader.h"
#include "XqLog.h"
#define  LOG_TAG    "LoadShader"

Shader::Shader(const char *vertexSource, const char *fragmentSource) {
    // Create and Compile Vertex Shader
    GLuint vertexShaderID = 0;
    LOGE(LOG_TAG,"rCompiling vertex shader code : %s",vertexSource);

    if (!compileShader(&vertexShaderID, GL_VERTEX_SHADER, vertexSource)) {
        LOGE(LOG_TAG,"compile vertex shader code error");
        return;
    }

    // Create and Compile Vertex Shader
    GLuint fragmentShaderID = 0;
    LOGE(LOG_TAG,"Compiling fragment shader code : %s",fragmentSource);
    if (!compileShader(&fragmentShaderID, GL_FRAGMENT_SHADER, fragmentSource)) {
        LOGE(LOG_TAG,"compile fragment shader code error");
        return;
    }

    //创建程序
    programID = glCreateProgram();

    // link the program object

    //
    // Attach vertex shader to program. 向程序中加入顶点着色器
    //
    glAttachShader(programID, vertexShaderID);
    //
    // Attach fragment shader to program.向程序中加入片元着色器
    //
    glAttachShader(programID, fragmentShaderID);

    // link shaders
    glLinkProgram(programID);
    if (!checkCompileErrors(programID, ShaderType::PROGRAM)) {
        //若链接失败则报错并删除程序
        LOGE(LOG_TAG,"Failed to link program: %d",programID);
        glDeleteShader(vertexShaderID);
        glDeleteShader(fragmentShaderID);
        programID = 0;
    } else {
        //
        // Release vertex and fragment shaders.
        //
        glDetachShader(programID, vertexShaderID);
        glDetachShader(programID, fragmentShaderID);
        glDeleteShader(vertexShaderID);
        glDeleteShader(fragmentShaderID);
    }

}


Shader::~Shader() {

}

void Shader::use() const {
    glUseProgram(programID);
}


int Shader::getHandle(const std::string &name, bool errorNotExist) {
    handleMapType::const_iterator it = handleMap.find(name);
    if (it != handleMap.end()) {
        return it->second;
    }

    int handle = glGetAttribLocation(programID, name.c_str());
    if (handle == -1) {
        handle = glGetUniformLocation(programID, name.c_str());
    }

    if (handle == -1 && errorNotExist) {
        LOGE(LOG_TAG,"compile fragment shader code error %s", name.c_str());
    } else {
        handleMap.insert(handleMapType::value_type(name, handle));
    }
    return handle;
}

void Shader::setBool(const std::string &name, bool value) {
    setUniform1i(name, (int) value);
}

void Shader::setInt(const std::string &name, int value) {
    setUniform1i(name, value);
}

void Shader::setFloat(const std::string &name, float value) {
    setUniform1f(name, value);
}

void Shader::setUniform1i(const std::string &name, GLint x) {
    GLuint location = getHandle(name);
    if (-1 != location) {
        glUniform1i(location, x);
    }
}


void Shader::setUniform2i(const std::string &name, GLint x, GLint y) {
    GLuint location = getHandle(name);
    if (-1 != location) {
        glUniform2i(location, x, y);
    }
}


void Shader::setUniform1iv(const std::string &name, int count, GLint *p) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform1iv(location, count, p);
    }
}

void Shader::setUniform1f(const std::string &name, GLfloat x) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform1f(location, x);
    }
}

void Shader::setUniform2f(const std::string &name, GLfloat x, GLfloat y) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform2f(location, x, y);
    }
}


void Shader::setUniform3f(const std::string &name, GLfloat x, GLfloat y, GLfloat z) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform3f(location, x, y, z);
    }
}


void Shader::setUniform4f(const std::string &name, GLfloat x, GLfloat y, GLfloat z, GLfloat w) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform4f(location, x, y, z, w);
    }
}

void Shader::setUniform1fv(const std::string &name, int count, GLfloat *p) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform1fv(location, count, p);
    }
}

void Shader::setUniform2fv(const std::string &name, int count, GLfloat *p) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform2fv(location, count, p);
    }
}

void Shader::setUniform4fv(const std::string &name, int count, GLfloat *p) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform4fv(location, count, p);
    }
}

void Shader::setUniformMatrix4fv(const std::string &name, GLsizei count, GLboolean transpose, const GLfloat *value) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniformMatrix4fv(location, count, transpose, value);
    }
}

void Shader::setUniformMatrix3fv(const std::string &name, GLsizei count, GLboolean transpose, const GLfloat *value) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniformMatrix3fv(location, count, transpose, value);
    }
}

void
Shader::setVertexAttribPointer(const std::string &name, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
                               void *ptr) {
    setVertexAttribPointer(getHandle(name), size, type, normalized, stride, ptr);
}

void Shader::setVertexAttrib(const std::string &name, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
                             int offset) {
    setVertexAttribPointer(name, size, type, normalized, stride, (void *) offset);
}

void Shader::disableVertexAttrib(const std::string &name) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glDisableVertexAttribArray(location);
    }
}

void Shader::setVertexAttribPointer(GLint location, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
                                    void *ptr) {
    if (-1 != location) {
        glEnableVertexAttribArray(location);
        glVertexAttribPointer(
                location, // attribute 0. No particular reason for 0, but must match the layout in the shader.属性0。没有特别的原因0，但必须匹配着色器中的布局。要指定数据的顶点属性数组的索引
                size, // size  每个顶点属性的数据个数。可能的取值是1、2、3或者4.初始值是4
                type, // type 数组中每个数据的类型。可能的取值是：GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, or GL_DOUBLE。初始值是GL_FLOAT。
                normalized,  // normalized?  指定顶点数在被访问的时候是否需要被归一化。
                stride,  // stride  两个连续顶点的属性之间的偏移量。这里从0索引开始取
                ptr // array buffer offset 指向数组中的第一个顶点属性的第一个数据。
                );
    }
}

void
Shader::setVertexAttrib(GLint location, GLint size, GLenum type, GLboolean normalized, GLsizei stride, int offset) {
    setVertexAttribPointer(location, size, type, normalized, stride, (void *) offset);
}

void Shader::setUniformTexture(const std::string &name, GLint x, GLuint textureID, GLenum target) {
    GLint location = getHandle(name);
    if (-1 != location) {
        glUniform1i(location, x);
        glActiveTexture(GL_TEXTURE0 + x);
        glBindTexture(target, textureID);
    }
}

int Shader::getUniformCount() {
    GLint nCount;
    glGetProgramiv(programID, GL_ACTIVE_UNIFORMS, &nCount);
    return nCount;
}

GLTexture* Shader::loadGLTexture(void *data,int width, int height) {
    unsigned int texture = 0;
    glGenTextures(1, &texture);
    checkGLError("glGenTextures");
    GLTexture *glTexture = (GLTexture *)malloc(sizeof(GLTexture));
    if (data != NULL && texture != 0) {
        glBindTexture(GL_TEXTURE_2D, texture);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
        checkGLError("texImage2D");
    }
    glTexture->textureId = texture;
    glTexture->width = width;
    glTexture->height = height;
    return glTexture;
}

void Shader::setTexture(const std::string &name, GLint x, GLTexture *texture) {
    GLint location = getHandle(name);
    if (-1 != location && texture->textureId != 0) {
        glUniform1i(location, x);
        glActiveTexture(GL_TEXTURE + x);
        glBindTexture(GL_TEXTURE_2D,texture->textureId);
    }
}


void Shader::checkGLError(const char *op) {
    for (GLint error = glGetError(); error; error = glGetError()) {
        std::cout << "after" << *op << "() glError (" << error << ")" << std::endl;
    }
}
/**
 * 创建着色器并编译
 * @param shader
 * @param type
 * @param source
 * @return
 */
bool Shader::compileShader(GLuint *shader, GLenum type, const char *source) {
    //创建一个着色器对象
    *shader = glCreateShader(type);
    //把这个着色器源码附加到着色器对象上
    glShaderSource(*shader, sizeof(source) / sizeof(&source), &source, NULL);
    //然后编译着色器
    glCompileShader(*shader);

    bool isSuccess = checkCompileErrors(*shader, type == GL_VERTEX_SHADER ? ShaderType::VERTEX : ShaderType::FRAGMENT);
    if (!isSuccess) {
        glDeleteShader(*shader);
        *shader = 0;
    }
    return isSuccess;
}

/**
 * 检测着色器错误
 * @param shaderOrProgramId
 * @param type
 * @return
 */
bool Shader::checkCompileErrors(GLuint shaderOrProgramId, ShaderType type) {
    GLint  logLength;
    GLint success;

    if (type != ShaderType::PROGRAM) {

        //返回着色器的信息日志的长度
        glGetShaderiv(shaderOrProgramId, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            std::vector<char> shaderErrorMessage(logLength + 1);
            glGetShaderInfoLog(shaderOrProgramId, logLength, NULL, &shaderErrorMessage[0]);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << &shaderErrorMessage[0]
                      << "\n -- --------------------------------------------------- -- " << std::endl;
        }

        //检测在调用glCompileShader后编译是否成功了
        glGetShaderiv(shaderOrProgramId, GL_COMPILE_STATUS, &success);
        if (!success) {
            std::vector<char> shaderErrorMessage(logLength + 1);
            glGetShaderInfoLog(shaderOrProgramId, logLength, NULL, &shaderErrorMessage[0]);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR1 of type: " << type << "\n" << &shaderErrorMessage[0]
                      << "\n -- --------------------------------------------------- -- " << std::endl;
            return false;
        }

    } else {
        //返回着色器的信息日志的长度
        glGetProgramiv(shaderOrProgramId, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            std::vector<char> programErrorMessage(logLength + 1);
            glGetProgramInfoLog(shaderOrProgramId, logLength, NULL, &programErrorMessage[0]);
            std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED of type: " << type << "\n" << &programErrorMessage[0]
                      << "\n -- --------------------------------------------------- -- " << std::endl;
        }

        // check for linking errors
        glGetProgramiv(shaderOrProgramId, GL_LINK_STATUS, &success);
        if (!success) {
            std::vector<char> programErrorMessage(logLength + 1);
            glGetProgramInfoLog(shaderOrProgramId, logLength, NULL, &programErrorMessage[0]);
            std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED2 of type: " << type << "\n" << &programErrorMessage[0]
                      << "\n -- --------------------------------------------------- -- " << std::endl;
            return false;
        }

    }

    return true;

}




